#ifndef HMS_MACROS_HPP
#define HMS_MACROS_HPP

#include "typeHelpers.hpp"

/* tells the compiler that a part of the code is unreachable, e.g. when a 
 * switch covers all enum values. If the compiler doesn't support such a
 * function, a std::logic_error is thrown upon reaching the supposedly
 * unreachable code. Make sure that the code is ACTUALLY unreachable! */
#if defined(__GNUC__) || defined(__clang__)
	#define UNREACHABLE \
		assert(false && "Unexpected branch!"); \
		__builtin_unreachable()
#else
	#define UNREACHABLE throw std::logic_error("Unreachable branch.")
#endif

/* forces the compiler to inline a function */
#if defined __GNUC__ || __clang__
#define HMS_INLINE __attribute__((always_inline)) inline
#else
#define HMS_INLINE
#endif

#define HMS_STRINGIFY_INTERNAL(ARG) #ARG
#define HMS_TO_STRING(ARG)  HMS_STRINGIFY_INTERNAL(ARG)

/* use OMP_PRAGMA(CLAUSES) instead of #ifdef _OPENMP #pragma omp CLAUSES #endif
 */
#ifdef _OPENMP
#include <omp.h>
#define PRAGMA_STRINGIFIER(ARG) _Pragma(#ARG)
#define OMP_PRAGMA(CLAUSES) PRAGMA_STRINGIFIER(omp CLAUSES)
#else
#define OMP_PRAGMA(CLAUSES)
#endif

#ifdef _OPENMP
#define OMP_PARALLEL_IF_NOT(CONDITION,OTHER_CLAUSES,BODY) \
if ( CONDITION ){ \
	OMP_PRAGMA(OTHER_CLAUSES) \
		BODY \
} else { \
	OMP_PRAGMA(parallel OTHER_CLAUSES) \
		BODY \
}
#else
#define OMP_PARALLEL_IF_NOT(CONDITION,OTHER_CLAUSES,BODY) BODY
#endif


/**
 * @brief Unifies Eigen::select and the conditional operator (?:), so that it
 * can be used both with boolean Eigen::DenseBase types and bools as condition.
 * It's implemented as an anonymous lambda, so it should be usable anywhere.
 * 
 */
#define HMS_SELECT(CONDITION, A, B) [&](){ \
using COND_TYPE = remove_qualifiers<decltype(CONDITION)>; \
if constexpr ( isDenseType<COND_TYPE>() ){ \
	return (CONDITION).select(A, B); \
} else { \
	return (CONDITION) ? A : B; \
} }()


#endif